package polimi.sc.expoquest.storage.datasources;

import java.util.ArrayList;
import java.util.Collection;

import polimi.sc.expoquest.storage.DBContract;
import polimi.sc.expoquest.storage.DBHelper;
import polimi.sc.expoquest.storage.model.GameOption;
import polimi.sc.expoquest.storage.model.PlayedGame;
import polimi.sc.expoquest.storage.model.SingleGame;
import polimi.sc.expoquest.storage.model.Tag;
import polimi.sc.expoquest.storage.model.User;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * TODO: Do we insert the tags of a played game in this class??
 * 
 * This class is in charge of managing all the database
 * operations related to the GOSSIP TABLE, using the 
 * database helper, which does the direct calls to the 
 * database
 * @author Monica Lozano
 *
 */
public class PlayedGameDataSource {
	//------------------
	//ATTRIBUTES
	//------------------
	
	public static final int NO_OPTION_ID =-1;
	
	/**
	 * The object that represents the database
	 */
	private SQLiteDatabase db;
	/**
	 * The database helper for accessing the database 
	 */
	private DBHelper dbHelper;
	
	//----------------
	// CONSTRUCTOR
	//----------------
	public PlayedGameDataSource(Context ctx)
	{
		//1. We create the DB helper
		dbHelper = new DBHelper(ctx);
	}
	
	//------------------
	//METHODS
	//------------------
	
	//database connection
	/**
	 * Opens the database such that we can used it for reading and writing
	 */
	public void open(){
		/* 
		 * TODO put this call in a asynchronous task, such that if it takes more than
		 * few seconds our application wont colapse.
		 */
		dbHelper.open();
		db = dbHelper.getWritableDatabase();
	}
	/**
	 * Close the database connection
	 */
	public void close()
	{
		dbHelper.close();
	}
	
	//-----------------------
	//Retrieving information
	//-----------------------
	
	/**
	 * @return Returns all the played games available, it doesnt return the related comments and other data. But it returns the ids
	 * for everything to which a specific played game is related (option, tags, etc)
	 */
	public Collection<PlayedGame> getAllPlayedGameByUser(int userID)
	{
		//1. Doing the consult
		String userIDS= String.valueOf(userID);
		Cursor cursor = db.query(DBContract.PlayGame.TABLE_NAME,DBContract.PlayGame.ALL_COLUMNS,
								DBContract.PlayGame.CLM_NAME_USER_ID + "= ?", new String[]{userIDS},null,null,null);
		//2. We create the list from the results of the query
		Collection<PlayedGame> pgs = buildCollectionFromCursor(cursor);
		return pgs;
	}
	
	/**
	 * @param playedGameID
	 * @return A PlayedGame structure with the basic data and all the IDs to which it is related such as the optionID, tagID, userID.
	 * In this way using the other data sources is possible to retrieve the remaining data.
	 */
	private PlayedGame getPlayedGame(long playedGameID)
	{
		//3. We return the alarm object saved succesfully into the database
		Cursor cursor = db.query(DBContract.PlayGame.TABLE_NAME, 
								DBContract.PlayGame.ALL_COLUMNS, 
								DBContract.PlayGame._ID+ "="+ playedGameID, 
								null, null, null, null);
		//3.1. Move the cursor to recent inserted row
		cursor.moveToFirst();
		//3.2. Creating the Alarm object
		PlayedGame pg = cursorToPlayedGame(cursor);
		//3.3 Closing the cursor
		cursor.close();
		return pg;
	}

	/**
	 * @param cursor
	 * @return Builds a collection of gossips based on the results pointed by the given cursor
	 */
	private Collection<PlayedGame> buildCollectionFromCursor(Cursor cursor)
	{
		Collection<PlayedGame> pgs = new ArrayList<PlayedGame>();
		cursor.moveToFirst();
		while(!cursor.isAfterLast())
		{
			PlayedGame pg = cursorToPlayedGame(cursor);
			pgs.add(pg);
			cursor.moveToNext();
		}
		cursor.close();
		return pgs;
	}
	
	
	/**
	 * Creates the PlayedGame object from the data in the current cursor
	 * @param cursor
	 * @return A PlayedGame structure with the basic data and all the IDs to which it is related such as the optionID, tagID, userID.
	 * In this way using the other data sources is possible to retrieve the remaining data
	 */
	private PlayedGame cursorToPlayedGame(Cursor cursor)
	{
		PlayedGame pg = new PlayedGame();
		//1. We obtain the columns indexes based on their names
		int idColIndex = cursor.getColumnIndex(DBContract.PlayGame._ID);
		int commColIndex = cursor.getColumnIndex(DBContract.PlayGame.CLM_NAME_COMMENT);
		int oiColIndex = cursor.getColumnIndex(DBContract.PlayGame.CLM_NAME_OPTION_ID);
		int pointsColIndex = cursor.getColumnIndex(DBContract.PlayGame.CLM_NAME_POINTS);
		int pictUColIndex = cursor.getColumnIndex(DBContract.PlayGame.CLM_NAME_PIC_URI);
		int uiColIndex = cursor.getColumnIndex(DBContract.PlayGame.CLM_NAME_USER_ID);
		int gameColIndex = cursor.getColumnIndex(DBContract.PlayGame.CLM_NAME_GAME_ID);
		//2.We obtain the data from the row based on the index
		int id =  cursor.getInt(idColIndex);
		String comment = cursor.getString(commColIndex);
		int optionID = cursor.getInt(oiColIndex);
		int points = cursor.getInt(pointsColIndex);
		String picURI = cursor.getString(pictUColIndex);
		int userID = cursor.getInt(uiColIndex);
		int gameID = cursor.getInt(gameColIndex);
		//3.We save the data in the object
		pg.setId(id);
		pg.setUser(new User (userID, null, null));
		SingleGame g = new SingleGame();
		g.setId(gameID);
		pg.setGame(g);
		pg.setOption(new GameOption(optionID, null));
		pg.setPicURI(picURI);
		pg.setPoints(points);
		pg.setComment(comment);
		return pg;
	}
	//-----------------------
	//Inserting information
	//-----------------------
	
	/**
	 * Inserts a played game of type trivia</b>
	 * Precondition: The optionID and userID are ids that ALREADY EXISTS on the database</b>
	 * Postcondition: The played games is inserted
	 * @param points: The points gain by the user
	 * @param optionID: An existing option that has been chosen by the user in the played game (type = trivia)
	 * @param userID: the user ID that is creating the gossip, ATTENTION: ID IS DIFFERENT FROM USERNAME
	 * @param gameID: The game ID to which the played game is associated
	 * @return
	 */
	public PlayedGame insertPlayedTrivia (int points, int optionID, int userID, int gameID)
	{
		//1.inserting the game
		PlayedGame pg = insert(null, null, points, optionID, userID, gameID);
		return pg;
	}
	
	/**
	 * Inserts a played game without tags</b>
	 * Precondition: The optionID and userID are ids that ALREADY EXISTS on the database</b>
	 * Postcondition: The played games is inserted
	 * @param comment: The comment of a game of type=comment
	 * @param points: The points gain by the user
	 * @param userID: the user ID that is creating the gossip, ATTENTION: ID IS DIFFERENT FROM USERNAME
	 * @param gameID: The game ID to which the played game is associated
	 * @return
	 */
	public PlayedGame insertPlayedComment (String comment, int points, int userID, int gameID)
	{
		//1.inserting the game
		PlayedGame pg = insert(comment, null, points, NO_OPTION_ID, userID, gameID);
		return pg;
	}
	
	/**
	 * Inserts a new played game with tags </b>
	 * Precondition: The tagIDS contains IDs of tags ALREADY created in the database in the table TAG ( and using the TagDataSource)</b>
	 * Postcondition: Inserts the played game in the table PLAYGAME and the related tags in the table PGAME_TAG
	 * @param comment
	 * @param picURI
	 * @param points
	 * @param userID
	 * @param gameID
	 * @param tagIDs
	 * @return The created playgame with its basic information, it only sends the IDs of the related objects (options, user, tags)
	 */
	public PlayedGame insertPlayedCamera(String picURI, int points,int userID, int gameID, Collection<Integer> tagIDs){
		
		//1.We save the played game
		PlayedGame pg = insert(null, picURI, points, NO_OPTION_ID, userID, gameID);
		//2.We save the related tags
		PlayedGame nPg  = insertRelatedTags(tagIDs, pg);
		//3. return
		return nPg;
	}
	

	/**
	 * Inserts a played game of type trivia</b>
	 * Precondition: The optionID and userID are ids that ALREADY EXISTS on the database</b>
	 * Postcondition: The played games is inserted
	 * @param comment: The comment of a game of type=comment
	 * @param picURI: the URI to a picture or simply null
	 * @param points: The points gain by the user
	 * @param optionID: An existing option that has been chosen by the user in the played game (type = trivia)
	 * @param userID: the user ID that is creating the gossip, ATTENTION: ID IS DIFFERENT FROM USERNAME
	 * @param gameID: The game ID to which the played game is associated
	 * @return
	 */
	private PlayedGame insert (String comment, String picURI, int points, int optionID, int userID, int gameID)
	{
		//1.We create the values for saving them into the database
		ContentValues values = generateValues(comment, picURI, points, optionID,userID, gameID);
		//2. We save it on the database
		long insertID= db.insert(DBContract.PlayGame.TABLE_NAME, null, values);
		//3. We return the alarm object saved succesfully into the database
		PlayedGame pg = getPlayedGame(insertID);
		return pg;
	}
	
	/**
	 * The values for creating a query for playgame table
	 * @param comment
	 * @param picURI
	 * @param optionID
	 * @param userID
	 * @param gameID
	 * @return
	 */
	private ContentValues generateValues(String comment, String picURI, int points, int optionID, int userID, int gameID)
	{
		ContentValues values = new ContentValues();
		//Comment value
		if(comment != null)
			values.put(DBContract.PlayGame.CLM_NAME_COMMENT, comment);
		else
			values.putNull(DBContract.PlayGame.CLM_NAME_COMMENT);
		//pic URI value
		if(picURI!= null)
			values.put(DBContract.PlayGame.CLM_NAME_PIC_URI, picURI);
		else
			values.putNull(DBContract.PlayGame.CLM_NAME_PIC_URI);
		//Points value
		values.put(DBContract.PlayGame.CLM_NAME_POINTS, points);
		//Option value
		if(optionID != NO_OPTION_ID)
			values.put(DBContract.PlayGame.CLM_NAME_OPTION_ID,optionID );
		else
			values.putNull(DBContract.PlayGame.CLM_NAME_OPTION_ID);
		//User id value
		values.put(DBContract.PlayGame.CLM_NAME_USER_ID, userID);
		//Game id value
		values.put(DBContract.PlayGame.CLM_NAME_GAME_ID, gameID);
		return values;
	}

	/**
	 * Inserts the tags ids in the table that mantaing the many=to=many relation
	 * TODO: Do the validation that the IDs in the tag table already exists..sqlite doesnt support foreign key enforcement natively :(
	 * @param tagIDs
	 * @return
	 */
	private PlayedGame insertRelatedTags(Collection<Integer> tagIDs, PlayedGame playGame)
	{
		ContentValues values= null;
		for (Integer tagID : tagIDs) {
				values = generateValuesForRTagsTable(tagID, playGame.getId());
				long insertID= db.insert(DBContract.PlayGameTag.TABLE_NAME,null, values);
				if (insertID>0) 
					//we add the tag into the play game object
					playGame.addTag(new Tag(tagID, null));
			}
		//Validating all the related tags where added
		int tsize = tagIDs.size();
		int pgSize = playGame.getTags().size();
		if(pgSize==tsize)
			return playGame;
		else{
			//Something bad happened, then we delete everything that we have created
			//TODO we need to also delete the created tags!
			deletePlayedGameWithTags(playGame);
			playGame.getTags().clear();
			return null;
		}
	}
	
	/**
	 * Generate the values for the creating a query to pgame_tag table
	 * @param tagID
	 * @param playGameID
	 * @return
	 */
	private ContentValues generateValuesForRTagsTable(int tagID,int playGameID)
	{
		ContentValues values = new ContentValues();
		values.put(DBContract.PlayGameTag.CLM_NAME_PGAME_ID, playGameID);
		values.put(DBContract.PlayGameTag.CLM_NAME_TAG_ID, tagID);

		return values;
	}
	/**
	 * Deletes a play game
	 * @param pg
	 * @return
	 */
	public boolean deletePlayedGame(PlayedGame pg)
	{
		long id = pg.getId();
		int total= db.delete(DBContract.PlayGame.TABLE_NAME,
				  DBContract.PlayGame._ID+"="+id, null);
		return total>0;
	}
	/**
	 * Deletes a played game with tags ids inserted in the many-to-may table
	 * @param pg
	 * @return
	 */
	public boolean deletePlayedGameWithTags(PlayedGame pg)
	{
		long id = pg.getId();
		//We delete the played game
		int t1= db.delete(DBContract.PlayGame.TABLE_NAME,
				  DBContract.PlayGame._ID+"="+id, null);
		//We delete the inserted tag ids 
		int t2= db.delete(DBContract.PlayGameTag.TABLE_NAME,
				  DBContract.PlayGameTag.CLM_NAME_PGAME_ID+"="+id, null);
		return t1>0 && t2>0;
	}
	
}
